/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the  "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * $Id$
 */
package wx.xml.xalan.xalan.templates;

import org.w3c.dom.Attr;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.TypeInfo;
import org.w3c.dom.UserDataHandler;
import org.xml.sax.SAXException;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.xml.transform.TransformerException;

import wx.xml.xalan.xalan.res.XSLMessages;
import wx.xml.xalan.xalan.res.XSLTErrorResources;
import wx.xml.xalan.xalan.transformer.TransformerImpl;
import wx.xml.xalan.xml.serializer.SerializationHandler;
import wx.xml.xalan.xml.utils.StringVector;
import wx.xml.xalan.xpath.XPathContext;

/**
 * Implement a Literal Result Element.
 *
 * @xsl.usage advanced
 * @see <a href="http://www.w3.org/TR/xslt#literal-result-element">literal-result-element in XSLT Specification</a>
 */
public class ElemLiteralResult extends ElemUse {
    static final long serialVersionUID = -8703409074421657260L;

    /**
     * The return value as Empty String.
     */
    private static final String EMPTYSTRING = "";

    /**
     * Tells if this element represents a root element
     * that is also the stylesheet element.
     * TODO: This should be a derived class.
     *
     * @serial
     */
    private boolean isLiteralResultAsStylesheet = false;
    /**
     * The created element node will have the attribute nodes
     * that were present on the element node in the stylesheet tree,
     * other than attributes with names in the XSLT namespace.
     *
     * @serial
     */
    private List m_avts = null;
    /**
     * List of attributes with the XSLT namespace.
     *
     * @serial
     */
    private List m_xslAttr = null;
    /**
     * The namespace of the element to be created.
     *
     * @serial
     */
    private String m_namespace;
    /**
     * The local name of the element to be created.
     *
     * @serial
     */
    private String m_localName;
    /**
     * The raw name of the element to be created.
     *
     * @serial
     */
    private String m_rawName;
    /**
     * The "extension-element-prefixes" property, actually contains URIs.
     *
     * @serial
     */
    private StringVector m_ExtensionElementURIs;
    /**
     * The XSLT version as specified by this element.
     *
     * @serial
     */
    private String m_version;
    /**
     * The "exclude-result-prefixes" property.
     *
     * @serial
     */
    private StringVector m_excludeResultPrefixes;

    /**
     * Return whether this element represents a root element
     * that is also the stylesheet element.
     *
     * @return boolean flag indicating whether this element
     * represents a root element that is also the stylesheet element.
     */
    public boolean getIsLiteralResultAsStylesheet() {
        return isLiteralResultAsStylesheet;
    }

    /**
     * Set whether this element represents a root element
     * that is also the stylesheet element.
     *
     * @param b boolean flag indicating whether this element
     *          represents a root element that is also the stylesheet element.
     */
    public void setIsLiteralResultAsStylesheet(boolean b) {
        isLiteralResultAsStylesheet = b;
    }

    /**
     * This function is called after everything else has been
     * recomposed, and allows the template to set remaining
     * values that may be based on some other property that
     * depends on recomposition.
     */
    public void compose(StylesheetRoot sroot) throws TransformerException {
        super.compose(sroot);
        StylesheetRoot.ComposeState cstate = sroot.getComposeState();
        java.util.Vector            vnames = cstate.getVariableNames();
        if (null != m_avts) {
            int nAttrs = m_avts.size();

            for (int i = (nAttrs - 1); i >= 0; i--) {
                AVT avt = (AVT) m_avts.get(i);
                avt.fixupVariables(vnames, cstate.getGlobalsSize());
            }
        }
    }

    /**
     * Set a literal result attribute (AVTs only).
     *
     * @param avt literal result attribute to add (AVT only)
     */
    public void addLiteralResultAttribute(AVT avt) {

        if (null == m_avts)
            m_avts = new ArrayList();

        m_avts.add(avt);
    }

    /**
     * Set a literal result attribute (used for xsl attributes).
     *
     * @param att literal result attribute to add
     */
    public void addLiteralResultAttribute(String att) {

        if (null == m_xslAttr)
            m_xslAttr = new ArrayList();

        m_xslAttr.add(att);
    }

    /**
     * Set the "xml:space" attribute.
     * A text node is preserved if an ancestor element of the text node
     * has an xml:space attribute with a value of preserve, and
     * no closer ancestor element has xml:space with a value of default.
     *
     * @param avt Enumerated value, either Constants.ATTRVAL_PRESERVE
     *            or Constants.ATTRVAL_STRIP.
     * @see <a href="http://www.w3.org/TR/xslt#strip">strip in XSLT Specification</a>
     * @see <a href="http://www.w3.org/TR/xslt#section-Creating-Text">section-Creating-Text in XSLT Specification</a>
     */
    public void setXmlSpace(AVT avt) {
        // This function is a bit-o-hack, I guess...
        addLiteralResultAttribute(avt);
        String val = avt.getSimpleString();
        if (val.equals("default")) {
            super.setXmlSpace(Constants.ATTRVAL_STRIP);
        } else if (val.equals("preserve")) {
            super.setXmlSpace(Constants.ATTRVAL_PRESERVE);
        }
        // else maybe it's a real AVT, so we can't resolve it at this time.
    }

    /**
     * Get a literal result attribute by name.
     *
     * @param namespaceURI Namespace URI of attribute node to get
     * @param localName    Local part of qualified name of attribute node to get
     * @return literal result attribute (AVT)
     */
    public AVT getLiteralResultAttributeNS(String namespaceURI, String localName) {

        if (null != m_avts) {
            int nAttrs = m_avts.size();

            for (int i = (nAttrs - 1); i >= 0; i--) {
                AVT avt = (AVT) m_avts.get(i);

                if (avt.getName().equals(localName) &&
                    avt.getURI().equals(namespaceURI)) {
                    return avt;
                }
            }  // end for
        }

        return null;
    }

    /**
     * Return the raw value of the attribute.
     *
     * @param namespaceURI Namespace URI of attribute node to get
     * @param localName    Local part of qualified name of attribute node to get
     * @return The Attr value as a string, or the empty string if that attribute
     * does not have a specified or default value
     */
    public String getAttributeNS(String namespaceURI, String localName) {

        AVT avt = getLiteralResultAttributeNS(namespaceURI, localName);

        if ((null != avt)) {
            return avt.getSimpleString();
        }

        return EMPTYSTRING;
    }

    /**
     * Get a literal result attribute by name. The name is namespaceURI:localname
     * if namespace is not null.
     *
     * @param name Name of literal result attribute to get
     * @return literal result attribute (AVT)
     */
    public AVT getLiteralResultAttribute(String name) {

        if (null != m_avts) {
            int    nAttrs    = m_avts.size();
            String namespace = null;
            for (int i = (nAttrs - 1); i >= 0; i--) {
                AVT avt = (AVT) m_avts.get(i);
                namespace = avt.getURI();

                if ((namespace != null && (namespace.length() != 0) && (namespace
                    + ":" + avt.getName()).equals(name)) || ((namespace == null ||
                    namespace.length() == 0) && avt.getRawName().equals(name))) {
                    return avt;
                }
            }  // end for
        }

        return null;
    }

    /**
     * Return the raw value of the attribute.
     *
     * @param namespaceURI:localName or localName if the namespaceURI is null of
     *                               the attribute to get
     * @return The Attr value as a string, or the empty string if that attribute
     * does not have a specified or default value
     */
    public String getAttribute(String rawName) {

        AVT avt = getLiteralResultAttribute(rawName);

        if ((null != avt)) {
            return avt.getSimpleString();
        }

        return EMPTYSTRING;
    }

    /**
     * Get whether or not the passed URL is flagged by
     * the "extension-element-prefixes" or "exclude-result-prefixes"
     * properties.
     *
     * @param prefix non-null reference to prefix that might be excluded.(not currently used)
     * @param uri    reference to namespace that prefix maps to
     * @return true if the prefix should normally be excluded.
     * @see <a href="http://www.w3.org/TR/xslt#extension-element">extension-element in XSLT Specification</a>
     */
    public boolean containsExcludeResultPrefix(String prefix, String uri) {
        if (uri == null ||
            (null == m_excludeResultPrefixes &&
                null == m_ExtensionElementURIs)
            )
            return super.containsExcludeResultPrefix(prefix, uri);

        if (prefix.length() == 0)
            prefix = Constants.ATTRVAL_DEFAULT_PREFIX;

        // This loop is ok here because this code only runs during
        // stylesheet compile time.
        if (m_excludeResultPrefixes != null)
            for (int i = 0; i < m_excludeResultPrefixes.size(); i++) {
                if (uri.equals(getNamespaceForPrefix(m_excludeResultPrefixes.elementAt(i))))
                    return true;
            }

        // JJK Bugzilla 1133: Also check locally-scoped extensions
        if (m_ExtensionElementURIs != null && m_ExtensionElementURIs.contains(uri))
            return true;

        return super.containsExcludeResultPrefix(prefix, uri);
    }

    /**
     * Augment resolvePrefixTables, resolving the namespace aliases once
     * the superclass has resolved the tables.
     *
     * @throws TransformerException
     */
    public void resolvePrefixTables() throws TransformerException {

        super.resolvePrefixTables();

        StylesheetRoot stylesheet = getStylesheetRoot();

        if ((null != m_namespace) && (m_namespace.length() > 0)) {
            NamespaceAlias nsa = stylesheet.getNamespaceAliasComposed(m_namespace);

            if (null != nsa) {
                m_namespace = nsa.getResultNamespace();

                // String resultPrefix = nsa.getResultPrefix();
                String resultPrefix = nsa.getStylesheetPrefix();  // As per xsl WG, Mike Kay

                if ((null != resultPrefix) && (resultPrefix.length() > 0))
                    m_rawName = resultPrefix + ":" + m_localName;
                else
                    m_rawName = m_localName;
            }
        }

        if (null != m_avts) {
            int n = m_avts.size();

            for (int i = 0; i < n; i++) {
                AVT avt = (AVT) m_avts.get(i);

                // Should this stuff be a method on AVT?
                String ns = avt.getURI();

                if ((null != ns) && (ns.length() > 0)) {
                    NamespaceAlias nsa =
                        stylesheet.getNamespaceAliasComposed(m_namespace); // %REVIEW% ns?

                    if (null != nsa) {
                        String namespace = nsa.getResultNamespace();

                        // String resultPrefix = nsa.getResultPrefix();
                        String resultPrefix = nsa.getStylesheetPrefix();  // As per XSL WG
                        String rawName      = avt.getName();

                        if ((null != resultPrefix) && (resultPrefix.length() > 0))
                            rawName = resultPrefix + ":" + rawName;

                        avt.setURI(namespace);
                        avt.setRawName(rawName);
                    }
                }
            }
        }
    }

    /**
     * Return whether we need to check namespace prefixes
     * against the exclude result prefixes or extensions lists.
     * Note that this will create a new prefix table if one
     * has not been created already.
     * <p>
     * NEEDSDOC ($objectName$) @return
     */
    boolean needToCheckExclude() {
        if (null == m_excludeResultPrefixes && null == getPrefixTable()
            && m_ExtensionElementURIs == null     // JJK Bugzilla 1133
            )
            return false;
        else {

            // Create a new prefix table if one has not already been created.
            if (null == getPrefixTable())
                setPrefixTable(new java.util.ArrayList());

            return true;
        }
    }

    /**
     * Get the original namespace of the Literal Result Element.
     * <p>
     * %REVIEW% Why isn't this overriding the getNamespaceURI method
     * rather than introducing a new one?
     *
     * @return The Namespace URI, or the empty string if the
     * element has no Namespace URI.
     */
    public String getNamespace() {
        return m_namespace;
    }

    /**
     * Set the namespace URI of the result element to be created.
     * Note that after resolvePrefixTables has been called, this will
     * return the aliased result namespace, not the original stylesheet
     * namespace.
     *
     * @param ns The Namespace URI, or the empty string if the
     *           element has no Namespace URI.
     */
    public void setNamespace(String ns) {
        if (null == ns) // defensive, shouldn't have to do this.
            ns = "";
        m_namespace = ns;
    }

    /**
     * Get the local name of the Literal Result Element.
     * Note that after resolvePrefixTables has been called, this will
     * return the aliased name prefix, not the original stylesheet
     * namespace prefix.
     *
     * @return The local name (without prefix) of the result element
     * to be created.
     */
    public String getLocalName() {
        return m_localName;
    }

    /**
     * Set the local name of the LRE.
     *
     * @param localName The local name (without prefix) of the result element
     *                  to be created.
     */
    public void setLocalName(String localName) {
        m_localName = localName;
    }

    /**
     * Get the raw name of the Literal Result Element.
     *
     * @return The qualified name (with prefix), or the
     * empty string if qualified names are not available.
     */
    public String getRawName() {
        return m_rawName;
    }

    /**
     * Set the raw name of the LRE.
     *
     * @param rawName The qualified name (with prefix), or the
     *                empty string if qualified names are not available.
     */
    public void setRawName(String rawName) {
        m_rawName = rawName;
    }

    /**
     * Get the prefix part of the raw name of the Literal Result Element.
     *
     * @return The prefix, or the empty string if noprefix was provided.
     */
    public String getPrefix() {
        int len = m_rawName.length() - m_localName.length() - 1;
        return (len > 0)
               ? m_rawName.substring(0, len)
               : "";
    }

    /**
     * Set the "extension-element-prefixes" property.
     *
     * @param v Vector of URIs (not prefixes) to set as the "extension-element-prefixes" property
     * @see <a href="http://www.w3.org/TR/xslt#extension-element">extension-element in XSLT Specification</a>
     */
    public void setExtensionElementPrefixes(StringVector v) {
        m_ExtensionElementURIs = v;
    }

    /**
     * @return NamedNodeMap
     * @see org.w3c.dom.Node
     */
    public NamedNodeMap getAttributes() {
        return new LiteralElementAttributes();
    }

    /**
     * Get an "extension-element-prefix" property.
     *
     * @param i Index of URI ("extension-element-prefix" property) to get
     * @return URI at given index ("extension-element-prefix" property)
     * @throws ArrayIndexOutOfBoundsException
     * @see <a href="http://www.w3.org/TR/xslt#extension-element">extension-element in XSLT Specification</a>
     */
    public String getExtensionElementPrefix(int i)
        throws ArrayIndexOutOfBoundsException {

        if (null == m_ExtensionElementURIs)
            throw new ArrayIndexOutOfBoundsException();

        return m_ExtensionElementURIs.elementAt(i);
    }

    /**
     * Get the number of "extension-element-prefixes" Strings.
     *
     * @return the number of "extension-element-prefixes" Strings
     * @see <a href="http://www.w3.org/TR/xslt#extension-element">extension-element in XSLT Specification</a>
     */
    public int getExtensionElementPrefixCount() {
        return (null != m_ExtensionElementURIs)
               ? m_ExtensionElementURIs.size() : 0;
    }

    /**
     * Find out if the given "extension-element-prefix" property is defined.
     *
     * @param uri The URI to find
     * @return True if the given URI is found
     * @see <a href="http://www.w3.org/TR/xslt#extension-element">extension-element in XSLT Specification</a>
     */
    public boolean containsExtensionElementURI(String uri) {

        if (null == m_ExtensionElementURIs)
            return false;

        return m_ExtensionElementURIs.contains(uri);
    }

    /**
     * Get an int constant identifying the type of element.
     *
     * @return The token ID for this element
     * @see wx.xml.xalan.xalan.templates.Constants
     */
    public int getXSLToken() {
        return Constants.ELEMNAME_LITERALRESULT;
    }

    /**
     * Return the node name.
     *
     * @return The element's name
     */
    public String getNodeName() {

        // TODO: Need prefix.
        return m_rawName;
    }

    /**
     * Get the "version" property.
     *
     * @return Version property value
     * @see <a href="http://www.w3.org/TR/xslt#forwards">forwards in XSLT Specification</a>
     */
    public String getVersion() {
        return m_version;
    }

    /**
     * Set the "version" property.
     *
     * @param v Version property value to set
     * @see <a href="http://www.w3.org/TR/xslt#forwards">forwards in XSLT Specification</a>
     */
    public void setVersion(String v) {
        m_version = v;
    }

    /**
     * Set the "exclude-result-prefixes" property.
     * The designation of a namespace as an excluded namespace is
     * effective within the subtree of the stylesheet rooted at
     * the element bearing the exclude-result-prefixes or
     * xsl:exclude-result-prefixes attribute; a subtree rooted
     * at an xsl:stylesheet element does not include any stylesheets
     * imported or included by children of that xsl:stylesheet element.
     *
     * @param v vector of prefixes that are resolvable to strings.
     * @see <a href="http://www.w3.org/TR/xslt#literal-result-element">literal-result-element in XSLT Specification</a>
     */
    public void setExcludeResultPrefixes(StringVector v) {
        m_excludeResultPrefixes = v;
    }

    /**
     * Tell if the result namespace decl should be excluded.  Should be called before
     * namespace aliasing (I think).
     *
     * @param prefix Prefix of namespace to check
     * @param uri    URI of namespace to check
     * @return True if the given namespace should be excluded
     * @throws TransformerException
     */
    private boolean excludeResultNSDecl(String prefix, String uri)
        throws TransformerException {

        if (null != m_excludeResultPrefixes) {
            return containsExcludeResultPrefix(prefix, uri);
        }

        return false;
    }

    /**
     * Copy a Literal Result Element into the Result tree, copy the
     * non-excluded namespace attributes, copy the attributes not
     * of the XSLT namespace, and execute the children of the LRE.
     *
     * @param transformer non-null reference to the the current transform-time state.
     * @throws TransformerException
     * @see <a href="http://www.w3.org/TR/xslt#literal-result-element">literal-result-element in XSLT Specification</a>
     */
    public void execute(TransformerImpl transformer)
        throws TransformerException {
        SerializationHandler rhandler = transformer.getSerializationHandler();

        try {
            if (transformer.getDebug()) {
                // flush any buffered pending processing before
                // the trace event.
                rhandler.flushPending();
                transformer.getTraceManager().fireTraceEvent(this);
            }

            // JJK Bugzilla 3464, test namespace85 -- make sure LRE's
            // namespace is asserted even if default, since xsl:element
            // may have changed the context.
            rhandler.startPrefixMapping(getPrefix(), getNamespace());

            // Add namespace declarations.
            executeNSDecls(transformer);
            rhandler.startElement(getNamespace(), getLocalName(), getRawName());
        } catch (SAXException se) {
            throw new TransformerException(se);
        }

        /*
         * If we make it to here we have done a successful startElement()
         * we will do an endElement() call for balance, no matter what happens
         * in the middle.
         */

        // tException remembers if we had an exception "in the middle"
        TransformerException tException = null;
        try {

            // Process any possible attributes from xsl:use-attribute-sets first
            super.execute(transformer);

            //xsl:version, excludeResultPrefixes???
            // Process the list of avts next
            if (null != m_avts) {
                int nAttrs = m_avts.size();

                for (int i = (nAttrs - 1); i >= 0; i--) {
                    AVT          avt        = (AVT) m_avts.get(i);
                    XPathContext xctxt      = transformer.getXPathContext();
                    int          sourceNode = xctxt.getCurrentNode();
                    String stringedValue =
                        avt.evaluate(xctxt, sourceNode, this);

                    if (null != stringedValue) {

                        // Important Note: I'm not going to check for excluded namespace
                        // prefixes here.  It seems like it's too expensive, and I'm not
                        // even sure this is right.  But I could be wrong, so this needs
                        // to be tested against other implementations.

                        rhandler.addAttribute(
                            avt.getURI(),
                            avt.getName(),
                            avt.getRawName(),
                            "CDATA",
                            stringedValue, false);
                    }
                } // end for
            }

            // Now process all the elements in this subtree
            // TODO: Process m_extensionElementPrefixes && m_attributeSetsNames
            transformer.executeChildTemplates(this, true);
        } catch (TransformerException te) {
            // thrown in finally to prevent original exception consumed by subsequent exceptions
            tException = te;
        } catch (SAXException se) {
            tException = new TransformerException(se);
        }

        try {
            /* we need to do this endElement() to balance the
             * successful startElement() call even if
             * there was an exception in the middle.
             * Otherwise an exception in the middle could cause a system to hang.
             */
            if (transformer.getDebug()) {
                // flush any buffered pending processing before
                // the trace event.
                //rhandler.flushPending();
                transformer.getTraceManager().fireTraceEndEvent(this);
            }
            rhandler.endElement(getNamespace(), getLocalName(), getRawName());
        } catch (SAXException se) {
            /* we did call endElement(). If thee was an exception
             * in the middle throw that one, otherwise if there
             * was an exception from endElement() throw that one.
             */
            if (tException != null)
                throw tException;
            else
                throw new TransformerException(se);
        }

        /* If an exception was thrown in the middle but not with startElement() or
         * or endElement() then its time to let it percolate.
         */
        if (tException != null)
            throw tException;

        unexecuteNSDecls(transformer);

        // JJK Bugzilla 3464, test namespace85 -- balance explicit start.
        try {
            rhandler.endPrefixMapping(getPrefix());
        } catch (SAXException se) {
            throw new TransformerException(se);
        }
    }

    /**
     * Compiling templates requires that we be able to list the AVTs
     * ADDED 9/5/2000 to support compilation experiment
     *
     * @return an Enumeration of the literal result attributes associated
     * with this element.
     */
    public Iterator enumerateLiteralResultAttributes() {
        return (null == m_avts) ? null : m_avts.iterator();
    }

    /**
     * Accept a visitor and call the appropriate method
     * for this class.
     *
     * @param visitor The visitor whose appropriate method will be called.
     * @return true if the children of the object should be visited.
     */
    protected boolean accept(XSLTVisitor visitor) {
        return visitor.visitLiteralResultElement(this);
    }

    /**
     * Call the children visitors.
     *
     * @param visitor The visitor whose appropriate method will be called.
     */
    protected void callChildVisitors(XSLTVisitor visitor, boolean callAttrs) {
        if (callAttrs && null != m_avts) {
            int nAttrs = m_avts.size();

            for (int i = (nAttrs - 1); i >= 0; i--) {
                AVT avt = (AVT) m_avts.get(i);
                avt.callVisitors(visitor);
            }
        }
        super.callChildVisitors(visitor, callAttrs);
    }

    /**
     * Throw a DOMException
     *
     * @param msg key of the error that occured.
     */
    public void throwDOMException(short code, String msg) {

        String themsg = XSLMessages.createMessage(msg, null);

        throw new DOMException(code, themsg);
    }

    public class LiteralElementAttributes implements NamedNodeMap {
        private int m_count = -1;

        /**
         * Construct a NameNodeMap.
         */
        public LiteralElementAttributes() {
        }

        /**
         * Return the number of Attributes on this Element
         *
         * @return The number of nodes in this map. The range of valid child
         * node indices is <code>0</code> to <code>length-1</code> inclusive
         */
        public int getLength() {
            if (m_count == -1) {
                if (null != m_avts)
                    m_count = m_avts.size();
                else
                    m_count = 0;
            }
            return m_count;
        }

        /**
         * Retrieves a node specified by name.
         *
         * @param name The <code>nodeName</code> of a node to retrieve.
         * @return A <code>Node</code> (of any type) with the specified
         * <code>nodeName</code>, or <code>null</code> if it does not
         * identify any node in this map.
         */
        public Node getNamedItem(String name) {
            if (getLength() == 0)
                return null;
            String uri       = null;
            String localName = name;
            int    index     = name.indexOf(":");
            if (-1 != index) {
                uri = name.substring(0, index);
                localName = name.substring(index + 1);
            }
            Node     retNode = null;
            Iterator eum     = m_avts.iterator();
            while (eum.hasNext()) {
                AVT avt = (AVT) eum.next();
                if (localName.equals(avt.getName())) {
                    String nsURI = avt.getURI();
                    if ((uri == null && nsURI == null)
                        || (uri != null && uri.equals(nsURI))) {
                        retNode = new Attribute(avt, ElemLiteralResult.this);
                        break;
                    }
                }
            }
            return retNode;
        }

        /**
         * Retrieves a node specified by local name and namespace URI.
         *
         * @param namespaceURI Namespace URI of attribute node to get
         * @param localName    Local part of qualified name of attribute node to
         *                     get
         * @return A <code>Node</code> (of any type) with the specified
         * <code>nodeName</code>, or <code>null</code> if it does not
         * identify any node in this map.
         */
        public Node getNamedItemNS(String namespaceURI, String localName) {
            if (getLength() == 0)
                return null;
            Node     retNode = null;
            Iterator eum     = m_avts.iterator();
            while (eum.hasNext()) {
                AVT avt = (AVT) eum.next();
                if (localName.equals(avt.getName())) {
                    String nsURI = avt.getURI();
                    if ((namespaceURI == null && nsURI == null)
                        || (namespaceURI != null && namespaceURI.equals(nsURI))) {
                        retNode = new Attribute(avt, ElemLiteralResult.this);
                        break;
                    }
                }
            }
            return retNode;
        }

        /**
         * Returns the <code>index</code>th item in the map. If <code>index
         * </code> is greater than or equal to the number of nodes in this
         * map, this returns <code>null</code>.
         *
         * @param i The index of the requested item.
         * @return The node at the <code>index</code>th position in the map,
         * or <code>null</code> if that is not a valid index.
         */
        public Node item(int i) {
            if (getLength() == 0 || i >= m_avts.size())
                return null;
            else
                return
                    new Attribute(((AVT) m_avts.get(i)),
                        ElemLiteralResult.this);
        }

        /**
         * @param name of the node to remove
         * @return The node removed from this map if a node with such
         * a name exists.
         * @throws DOMException
         * @see org.w3c.dom.NamedNodeMap
         */
        public Node removeNamedItem(String name) throws DOMException {
            throwDOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR,
                XSLTErrorResources.NO_MODIFICATION_ALLOWED_ERR);
            return null;
        }

        /**
         * @param namespaceURI Namespace URI of the node to remove
         * @param localName    Local part of qualified name of the node to remove
         * @return The node removed from this map if a node with such a local
         * name and namespace URI exists
         * @throws DOMException
         * @see org.w3c.dom.NamedNodeMap
         */
        public Node removeNamedItemNS(String namespaceURI, String localName)
            throws DOMException {
            throwDOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR,
                XSLTErrorResources.NO_MODIFICATION_ALLOWED_ERR);
            return null;
        }

        /**
         * Unimplemented. See org.w3c.dom.NamedNodeMap
         *
         * @param A node to store in this map
         * @return If the new Node replaces an existing node the replaced
         * Node is returned, otherwise null is returned
         * @throws DOMException
         */
        public Node setNamedItem(Node arg) throws DOMException {
            throwDOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR,
                XSLTErrorResources.NO_MODIFICATION_ALLOWED_ERR);
            return null;
        }

        /**
         * Unimplemented. See org.w3c.dom.NamedNodeMap
         *
         * @param A node to store in this map
         * @return If the new Node replaces an existing node the replaced
         * Node is returned, otherwise null is returned
         * @throws DOMException
         */
        public Node setNamedItemNS(Node arg) throws DOMException {
            throwDOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR,
                XSLTErrorResources.NO_MODIFICATION_ALLOWED_ERR);
            return null;
        }
    }

    public class Attribute implements Attr {
        private AVT     m_attribute;
        private Element m_owner = null;

        /**
         * Construct a Attr.
         */
        public Attribute(AVT avt, Element elem) {
            m_attribute = avt;
            m_owner = elem;
        }

        /**
         * @param newChild New node to append to the list of this node's
         *                 children
         * @throws DOMException
         * @see org.w3c.dom.Node
         */
        public Node appendChild(Node newChild) throws DOMException {
            throwDOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR,
                XSLTErrorResources.NO_MODIFICATION_ALLOWED_ERR);
            return null;
        }

        /**
         * @param deep Flag indicating whether to clone deep
         *             (clone member variables)
         * @return Returns a duplicate of this node
         * @see org.w3c.dom.Node
         */
        public Node cloneNode(boolean deep) {
            return new Attribute(m_attribute, m_owner);
        }

        /**
         * @return null
         * @see org.w3c.dom.Node
         */
        public NamedNodeMap getAttributes() {
            return null;
        }

        /**
         * @return a NodeList containing no nodes.
         * @see org.w3c.dom.Node
         */
        public NodeList getChildNodes() {
            return new NodeList() {
                public int getLength() {
                    return 0;
                }

                public Node item(int index) {
                    return null;
                }
            };
        }

        /**
         * @return null
         * @see org.w3c.dom.Node
         */
        public Node getFirstChild() {
            return null;
        }

        /**
         * @return null
         * @see org.w3c.dom.Node
         */
        public Node getLastChild() {
            return null;
        }

        /**
         * @return the local part of the qualified name of this node
         * @see org.w3c.dom.Node
         */
        public String getLocalName() {
            return m_attribute.getName();
        }

        /**
         * @return The namespace URI of this node, or null if it is
         * unspecified
         * @see org.w3c.dom.Node
         */
        public String getNamespaceURI() {
            String uri = m_attribute.getURI();
            return (uri.length() == 0) ? null : uri;
        }

        /**
         * @return null
         * @see org.w3c.dom.Node
         */
        public Node getNextSibling() {
            return null;
        }

        /**
         * @return The name of the attribute
         * @see org.w3c.dom.Node
         */
        public String getNodeName() {
            String uri       = m_attribute.getURI();
            String localName = getLocalName();
            return (uri.length() == 0) ? localName : uri + ":" + localName;
        }

        /**
         * @return The node is an Attr
         * @see org.w3c.dom.Node
         */
        public short getNodeType() {
            return ATTRIBUTE_NODE;
        }

        /**
         * @return The value of the attribute
         * @throws DOMException
         * @see org.w3c.dom.Node
         */
        public String getNodeValue() throws DOMException {
            return m_attribute.getSimpleString();
        }

        /**
         * @param nodeValue Value to set this node to
         * @throws DOMException
         * @see org.w3c.dom.Node
         */
        public void setNodeValue(String nodeValue) throws DOMException {
            throwDOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR,
                XSLTErrorResources.NO_MODIFICATION_ALLOWED_ERR);
        }

        /**
         * @return null
         * @see org.w3c.dom.Node
         */
        public Document getOwnerDocument() {
            return m_owner.getOwnerDocument();
        }

        /**
         * @return the containing element node
         * @see org.w3c.dom.Node
         */
        public Node getParentNode() {
            return m_owner;
        }

        /**
         * @return The namespace prefix of this node, or null if it is
         * unspecified
         * @see org.w3c.dom.Node
         */
        public String getPrefix() {
            String uri     = m_attribute.getURI();
            String rawName = m_attribute.getRawName();
            return (uri.length() == 0) ?
                   null : rawName.substring(0, rawName.indexOf(":"));
        }

        /**
         * @param prefix Prefix to set for this node
         * @throws DOMException
         * @see org.w3c.dom.Node
         */
        public void setPrefix(String prefix) throws DOMException {
            throwDOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR,
                XSLTErrorResources.NO_MODIFICATION_ALLOWED_ERR);
        }

        /**
         * @return null
         * @see org.w3c.dom.Node
         */
        public Node getPreviousSibling() {
            return null;
        }

        /**
         * @return false
         * @see org.w3c.dom.Node
         */
        public boolean hasAttributes() {
            return false;
        }

        /**
         * @return false
         * @see org.w3c.dom.Node
         */
        public boolean hasChildNodes() {
            return false;
        }

        /**
         * @param newChild New child node to insert
         * @param refChild Insert in front of this child
         * @return null
         * @throws DOMException
         * @see org.w3c.dom.Node
         */
        public Node insertBefore(Node newChild, Node refChild)
            throws DOMException {
            throwDOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR,
                XSLTErrorResources.NO_MODIFICATION_ALLOWED_ERR);
            return null;
        }

        /**
         * @return Returns <code>false</code>
         * @see org.w3c.dom.Node
         * @since DOM Level 2
         */
        public boolean isSupported(String feature, String version) {
            return false;
        }

        /**
         * @see org.w3c.dom.Node
         */
        public void normalize() {
        }

        /**
         * @param oldChild Child to be removed
         * @return null
         * @throws DOMException
         * @see org.w3c.dom.Node
         */
        public Node removeChild(Node oldChild) throws DOMException {
            throwDOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR,
                XSLTErrorResources.NO_MODIFICATION_ALLOWED_ERR);
            return null;
        }

        /**
         * @param newChild Replace existing child with this one
         * @param oldChild Existing child to be replaced
         * @return null
         * @throws DOMException
         * @see org.w3c.dom.Node
         */
        public Node replaceChild(Node newChild, Node oldChild) throws DOMException {
            throwDOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR,
                XSLTErrorResources.NO_MODIFICATION_ALLOWED_ERR);
            return null;
        }

        /**
         * @return The name of this attribute
         */
        public String getName() {
            return m_attribute.getName();
        }

        /**
         * @return The value of this attribute returned as string
         */
        public String getValue() {
            return m_attribute.getSimpleString();
        }

        /**
         * @param value Value to set this node to
         * @throws DOMException
         * @see org.w3c.dom.Attr
         */
        public void setValue(String value) throws DOMException {
            throwDOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR,
                XSLTErrorResources.NO_MODIFICATION_ALLOWED_ERR);
        }

        /**
         * @return The Element node this attribute is attached to
         * or null if this attribute is not in use
         */
        public Element getOwnerElement() {
            return m_owner;
        }

        /**
         * @return true
         */
        public boolean getSpecified() {
            return true;
        }

        public TypeInfo getSchemaTypeInfo() {
            return null;
        }

        public boolean isId() {
            return false;
        }

        public Object setUserData(String key,
                                  Object data,
                                  UserDataHandler handler) {
            return getOwnerDocument().setUserData(key, data, handler);
        }

        public Object getUserData(String key) {
            return getOwnerDocument().getUserData(key);
        }

        public Object getFeature(String feature, String version) {
            return isSupported(feature, version) ? this : null;
        }

        public boolean isEqualNode(Node arg) {
            return arg == this;
        }

        public String lookupNamespaceURI(String specifiedPrefix) {
            return null;
        }

        public boolean isDefaultNamespace(String namespaceURI) {
            return false;
        }

        public String lookupPrefix(String namespaceURI) {
            return null;
        }

        public boolean isSameNode(Node other) {
            // we do not use any wrapper so the answer is obvious
            return this == other;
        }

        public String getTextContent() throws DOMException {
            return getNodeValue();  // overriden in some subclasses
        }

        public void setTextContent(String textContent)
            throws DOMException {
            setNodeValue(textContent);
        }

        public short compareDocumentPosition(Node other) throws DOMException {
            return 0;
        }

        public String getBaseURI() {
            return null;
        }
    }

}
